/*
 * Copyright 2012 Walter Lütgenau.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package offset.nodes.client.veditor.controller;

import java.awt.Color;
import java.awt.Component;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.JSeparator;
import javax.swing.JToolBar.Separator;
import javax.swing.border.BevelBorder;
import javax.swing.text.Element;
import javax.swing.text.html.CSS;
import offset.nodes.Constants;
import offset.nodes.client.editor.Editor;
import offset.nodes.client.editor.controller.component.ComponentKit;
import offset.nodes.client.editor.model.ComponentModel;
import offset.nodes.client.editor.model.DocumentContext;
import offset.nodes.client.model.component.ComponentEditorEnvironment;
import offset.nodes.client.veditor.VirtualEditor;
import offset.nodes.client.veditor.model.DataModel;
import offset.nodes.client.view.component.ComponentEditor;

/**
 *
 * @author Walter Lütgenau
 */
public class VirtualComponentKit extends ComponentKit {

    public VirtualComponentKit(Editor editor) {
        super(editor);
        Color c = Color.GRAY;
        setFocusBorder(new BevelBorder(BevelBorder.LOWERED, c.brighter(), c.darker()));
        setNonFocusColor(getFocusColor());
        setNonFocusBorder(BorderFactory.createEmptyBorder());
    }

    protected void decoratePane(Element element, JComponent pane) {
        if (element.getAttributes().isDefined(CSS.Attribute.BORDER_STYLE)) {
            pane.setBorder(getFocusBorder());
        } else {
            pane.setBorder(getNonFocusBorder());
        }
    }

    /**
     * Create the applet component implement ComponentFrame from the information in the element.
     *
     * If the component element has a UUID, use the inherited method. If not, the element
     * corresponds to a new node, that shall be created. In that case, determine the UUID of the
     * root data element and the relative path from that element. Use this to create the component
     * editor environment instead.
     *
     * @param element
     * @return the initialized applet with the simulated environment.
     */
    protected ComponentEditor createAppletComponent(Element element) {
        try {
            String uuid = (String) element.getAttributes().getAttribute(ComponentModel.ATT_COMPONENT_ID);
            if (uuid == null || uuid.indexOf(DataModel.ATTRIBUTE_VALUE_COMPONENT_ID) < 0) {
                return super.createAppletComponent(element);
            }

            String id = getRootUuid(element);
            if (id == null) {
                return null;
            }

            String documentBase = getDocumentBase();
            if (documentBase == null) {
                return null;
            }

            String relativePath = getRelativePath(element);
            if (relativePath == null) {
                return null;
            }

            String dialogUrl = (String) getEditor().getProperties().get(Editor.PROP_UPLOAD_TO);

            ComponentEditorEnvironment componentEditor = new ComponentEditorEnvironment(new URL(documentBase), new URL(dialogUrl), id, relativePath);
            componentEditor.init();
            if (componentEditor.getComponent() == null
                    || !(componentEditor.getComponent() instanceof ComponentEditor)) {
                return null;
            }
            return (ComponentEditor) componentEditor.getComponent();
        } catch (Exception ex) {
            Logger.getLogger(ComponentKit.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }
    }

    /**
     * Return the UUID of the root node. The root element (the highest DIV element) has an attribute
     * "n-id" in addition to the data attribute with the value of the root node's UUID.
     *
     * @return the UUID of the root node.
     */
    protected String getRootUuid(Element element) {
        DataModel model = getModel();
        return getModel().getId(getRootElement(element));
    }

    /**
     * Return the UUID of the root node. The root element is the highest element with a n-data
     * attribute
     *
     * @return the root element.
     */
    protected Element getRootElement(Element e) {
        DataModel model = getModel();

        Element root = e;
        while (e != null) {
            if (model.hasData(e)) {
                root = e;
            }
            e = e.getParentElement();
        }

        return root;
    }

    /**
     * Return the document base as a string.
     *
     * @return the document base
     */
    protected String getDocumentBase() throws UnsupportedEncodingException {
        try {
            String document = (String) getEditor().getProperties().get(Editor.PROP_DOCUMENT_URL);
            if (document == null) {
                return null;
            }
            URL documentURL = new URL(document);
            String path = urlEncode(documentURL.getPath());
            String[] dirs = path.split(Constants.PATH_SEPARATOR);
            path = Constants.PATH_SEPARATOR + dirs[1] + Constants.PATH_SEPARATOR + dirs[2];
            URL documentBaseURL = new URL(documentURL.getProtocol(), documentURL.getHost(), documentURL.getPort(), path);
            return documentBaseURL.toString();
        } catch (MalformedURLException ex) {
            return null;
        }
    }

    /**
     * Return the relative data path from the root element to the element with the binary property.
     *
     * @return the relative path
     */
    protected String getRelativePath(Element element) {
        DataModel model = getModel();
        String path = model.getData(element);
        if (path == null || path.length() == 0) {
            return path;
        }

        String rootPath = model.getData(getRootElement(element));

        return urlEncode(path.substring(rootPath.length() + 1));
    }

    /**
     * Return the data path of the root element
     *
     * @return the relative path
     */
    protected String getPath(Element element) {
        DataModel model = getModel();

        if (element.getParentElement() == null) {
            return null;
        }

        element = element.getParentElement();
        while (!model.isData(element) && element.getParentElement() != null) {
            element = element.getParentElement();
        }

        String path = model.getData(element);

        // Check for multiple occuring elements
        String[] dirs = path.split(Constants.PATH_SEPARATOR);
        for (int i = dirs.length - 1; i > 0; i--) {
            String currentPath = createPath(dirs, i);
        }

        return createPath(dirs, dirs.length - 1);
    }

    /**
     * Create a path of the path components, i.e. concatenate them separated by a path separator.
     * Limit this to at most length components.
     *
     * @param dirs
     * @param length
     * @return
     */
    protected String createPath(String[] dirs, int length) {
        StringBuilder buf = new StringBuilder();
        for (int i = 0; i <= length; i++) {
            buf.append(dirs[i]);
            if (i + 1 <= length) {
                buf.append(Constants.PATH_SEPARATOR);
            }
        }

        return buf.toString();
    }

    /**
     * URL encode the string
     *
     * @param value the string to encode
     * @return the URL encoded string
     */
    protected String urlEncode(String value) {
        return value.replaceAll(" ", "\\+");
    }

    /**
     * Create a DataModel
     *
     * @return the DataModel
     */
    protected DataModel getModel() {
        return new DataModel(new DocumentContext(getEditor().getBrowserPane()), getEditor().getProperties());
    }

    /**
     * Use specific extension of ComponentUI.
     *
     * @param componentEditor the component editor to create a UI for.
     * @return VirtualComponentUI.
     */
    protected ComponentUI createComponentUI(ComponentEditor componentEditor) {
        return new VirtualComponentUI(componentEditor);
    }

    /**
     * Extensions for the virtual editor to the ComponentUI
     */
    class VirtualComponentUI extends ComponentUI {

        public VirtualComponentUI(ComponentEditor component) {
            super(component);
        }

        /* problems with KeyboardFocusManager */
        protected void addComponentActions() {
            if (getComponent() == getEditor()) {
                return;
            }

            JButton backTab = new JButton();
            backTab.setAction(new DataActions.BackTabAction((VirtualEditor) getEditor()));
            backTab.setText("");
            getToolbarButtons().add(backTab);
            backTab.setVisible(false);

            JButton tab = new JButton();
            tab.setAction(new DataActions.TabAction((VirtualEditor) getEditor()));
            tab.setText("");
            getToolbarButtons().add(tab);
            tab.setVisible(false);

            super.addComponentActions();

            for (Component component : getEditor().getFileMenu().getMenuComponents()) {
                if (component instanceof JMenuItem) {
                    JMenuItem editorMenuItem = (JMenuItem) component;

                    JButton menuButton = new JButton();
                    menuButton.setAction(editorMenuItem.getAction());
                    menuButton.setText("");
                    menuButton.setVisible(false);

                    getToolbarButtons().add(menuButton);
                } else if (component instanceof JSeparator) {
                    JSeparator separator = new JSeparator();
                    separator.setVisible(false);
                    getToolbarButtons().add(separator);
                }
            }

            JSeparator separator = new Separator();
            separator.setVisible(false);
            getToolbarButtons().add(separator);
        }
    }
}
